package cn.cdm.controller;

import cn.cdm.domain.MiaoshaOrder;
import cn.cdm.domain.MiaoshaUser;
import cn.cdm.domain.OrderInfo;
import cn.cdm.exception.GlobalException;
import cn.cdm.rabbitmq.MQSender;
import cn.cdm.rabbitmq.MiaoshaMessage;
import cn.cdm.redis.GoodsKey;
import cn.cdm.redis.MiaoshaUserKey;
import cn.cdm.redis.OrderKey;
import cn.cdm.redis.RedisService;
import cn.cdm.result.CodeMsg;
import cn.cdm.result.Result;
import cn.cdm.service.GoodsService;
import cn.cdm.service.MiaoshaService;
import cn.cdm.service.MiaoshaUserService;
import cn.cdm.service.OrderService;
import cn.cdm.vo.GoodsVo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Controller
@RequestMapping("/miaosha")
public class MiaoshaController implements InitializingBean {
	private static final Logger log = LoggerFactory.getLogger(MiaoshaController.class);

	@Autowired
	MiaoshaUserService userService;
	
	@Autowired
	RedisService redisService;
	
	@Autowired
	GoodsService goodsService;
	
	@Autowired
	OrderService orderService;
	
	@Autowired
	MiaoshaService miaoshaService;

	@Autowired
	MQSender sender;

	// 维护一个秒杀结束标志。key为商品id，value为秒杀结束的标志，true表示秒杀已经结束；false表示秒杀进行中。
	private final Map<Long, Boolean> localOverMap = new ConcurrentHashMap<>(10);

	/**
	 * 项目启动时，调用该方法，做下面2件事
	 * 1、将秒杀商品的库存放入到redis缓存中，也就是我们常听说的 缓存预热。
	 * 2、将商品列表中的所有秒杀商品，做了标记，表示商品列表中的商品秒杀活动没有结束。
	 * @throws Exception
	 */
	@Override
	public void afterPropertiesSet() throws Exception {
		List<GoodsVo> goodsVos = goodsService.listGoodsVo();
		if(goodsVos == null){
			return;
		}
		for (GoodsVo goodsVo : goodsVos) {
			redisService.set(GoodsKey.goodStock, "" + goodsVo.getId(), goodsVo.getStockCount());
			localOverMap.put(goodsVo.getId(), false);
		}
	}

	/**
	 * 重置秒杀活动的接口:
	 * 做完秒杀后，编写reset接口，删除Redis的缓存、Mysql数据库的相关表，还原得到秒杀之前的状态信息
	 * @param model
	 * @return
	 */
	@RequestMapping(value = "/reset", method = RequestMethod.GET)
	@ResponseBody
	public Result<Boolean> reset(Model model) {
		List<GoodsVo> goodsVoList = goodsService.listGoodsVo();
		for (GoodsVo goodsVo : goodsVoList) {
			goodsVo.setStockCount(10);
			redisService.set(GoodsKey.goodStock, "" + goodsVo.getId(), 10);
			localOverMap.put(goodsVo.getId(), false);
		}
		// 删除其他的Redis缓存
		redisService.delete(GoodsKey.orderInfo);
		// 删除订单表、秒杀订单表
		miaoshaService.reset(goodsVoList);

		return Result.success(true);
	}

	/**
	 * 前端每隔1秒钟轮询该接口，若该接口返回 -1，表示秒杀失败；0表示排列中；其他，则返回秒杀的订单号。具体如下：
	 * orderId：成功
	 * -1：秒杀失败
	 * 0： 排队中
	 */
//	@AccessLimit(seconds = 5, maxCount = 5, needLogin = true)
	@RequestMapping(value = "/result", method = RequestMethod.GET)
	@ResponseBody
	public Result<Long> miaoshaResult(Model model, MiaoshaUser user,
										   @RequestParam("goodsId") long goodsId) {
		Long orderId = miaoshaService.getMiaoshaResult(Long.valueOf(user.getNickname()), goodsId);
		return Result.success(orderId);
	}


	/**
	 * 第三版程序：使用内存标记、Redis、MQ，后端返回json格式给前端
	 *
	 * 5000个并发，10次循环
	 * QPS：3168；3320；3073；3462
	 * @param user
	 * @param goodsId
	 * @return
	 */
	@RequestMapping("/do_miaosha")
	@ResponseBody // 该注解表示：响应数据，以JSON格式返回给前端
	public Result<Integer> miaosha(MiaoshaUser user, @RequestParam("goodsId")long goodsId) {

		if(user == null) {
			return Result.error(CodeMsg.SESSION_ERROR);
		}

		// 当商品id不在map中时，会报错 NullPointerException异常
//		boolean over = localOverMap.get(goodsId);
//		if (over) {
//			return Result.error(CodeMsg.MIAO_SHA_OVER);
//		}

		// 使用内存标记方法，减少对Redis的访问
		Boolean over = localOverMap.get(goodsId);
		if (over == null) {
			// 当localOverMap中不存在该商品id的键时，得到的over为null，我们直接返回该秒杀商品不存在，用于提示用户
			return  Result.error(CodeMsg.MIAO_SHA_GOODS_NOT_EXIST);
		} else if (over) {
			return Result.error(CodeMsg.MIAO_SHA_OVER);
		}

		// 判断是否已经秒杀到了，防止某个用户重复秒杀
		MiaoshaOrder miaoshaOrder = redisService.get(GoodsKey.orderInfo, "" + user.getId() + "-" + goodsId, MiaoshaOrder.class);
		if(miaoshaOrder == null){
			// 若Redis中的该用户的秒杀订单为空，则再从MySQL中查询
			miaoshaOrder = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
			if(miaoshaOrder != null) {
				return Result.error(CodeMsg.REPEAT_MIAOSHA);
			}
		} else {
			return Result.error(CodeMsg.REPEAT_MIAOSHA);
		}

		// 预减库存：当不是某个用户的重复秒杀，才去预减库存，预减库存是有条件的 todo 很Bug的一个操作，后续需要对其进行维护，如何保证Redis中秒杀商品库存的正确性？？
		Long stock = redisService.decr(GoodsKey.goodStock, "" + goodsId);
		if(stock < 0) {
			// 维护一个秒杀结束标志：当该商品的库存小于0时，将该商品设置为秒杀完毕，即设置该商品的内存标记为true
			localOverMap.put(goodsId, true);
			return Result.error(CodeMsg.MIAO_SHA_OVER);
		}

		// 入队：进入RabbitMQ的消息队列。也可以采用JDK自带的阻塞队列，来实现生产者-消费者模型。
		MiaoshaMessage miaoshaMessage = new MiaoshaMessage(user, goodsId);
		sender.sendMiaoshaMessage(miaoshaMessage);

		// 返回排队中。0表示排队中；小于0，表示秒杀失败；大于0，则表示用户秒杀成功！
		return Result.success(0);
	}

	/**
	 * 第二版程序：使用了内存标记、Redis、MQ，后端返回html页面
	 *
	 * 5000个并发，10次循环
	 * QPS：1360
	 *本机测试：575；
	 *
	 * GET、POST区别如下：
	 * GET 幂等。
	 * POST 向服务器提交表单数据，非幂等。
	 * @param
	 * @param user
	 * @param goodsId
	 * @return
	 */
    /*@RequestMapping("/do_miaosha") //  @RequestMapping注解，默认是GET或POST都可以
    public String list(Model model, MiaoshaUser user,
					   @RequestParam("goodsId")long goodsId) {

    	log.info("当前秒杀用户: " + user);
    	model.addAttribute("user", user);
    	if(user == null) {
    		// 当用户为空时，返回到登录页面
    		return "login";
    	}

    	// 使用内存标记方法，减少对Redis的访问
		boolean over = localOverMap.get(goodsId);
    	if (over) {
			throw new GlobalException(CodeMsg.MIAO_SHA_OVER);
		}

		// 判断是否已经秒杀到了，防止某个用户重复秒杀
		MiaoshaOrder miaoshaOrder = redisService.get(GoodsKey.orderInfo, "" + user.getId() + "-" + goodsId, MiaoshaOrder.class);
		if(miaoshaOrder == null){
			// 若Redis中的该用户的秒杀订单为空，则再从MySQL中查询
			MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
			if(order != null) {
				log.error("打印：{}",CodeMsg.REPEAT_MIAOSHA);
				model.addAttribute("errmsg", CodeMsg.REPEAT_MIAOSHA.getMsg());
				return "miaosha_fail";
			}
		} else {
			model.addAttribute("errmsg", CodeMsg.REPEAT_MIAOSHA.getMsg());
			log.warn("用户id：{}， {}", user.getId(), CodeMsg.REPEAT_MIAOSHA);
			return "miaosha_fail";
		}

		// 预减库存：收到请求，Redis预减库存；当库存不足，直接返回
		Long stock = redisService.decr(GoodsKey.goodStock, "" + goodsId);
		if(stock < 0) {
			log.error("Redis中的预减库存，预减库存之后的数量：{}", stock);
			// 维护一个秒杀结束标志：当该商品的库存小于0时，将该商品设置为秒杀结束，即设置该商品的内存标记为true
			localOverMap.put(goodsId, true);
			return "miaosha_fail";
		}

    	// 入队
		MiaoshaMessage miaoshaMessage = new MiaoshaMessage(user, goodsId);
		sender.sendMiaoshaMessage(miaoshaMessage);

		log.info("秒杀请求排队中，请稍后，用户id：{}；商品id：{}", user.getId(), goodsId);
//        return "order_detail";
        return "hello";
    }*/


	/**
	 * 第一版程序：只使用数据库
	 *
	 * 优化之前的秒杀接口
	 * 5000个并发，10次循环，即5万个请求
	 * 	 * QPS：833；874；908
	 * @param model
	 * @param user
	 * @param goodsId
	 * @return
	 */
	/*@RequestMapping(value="/do_miaosha")
	@ResponseBody
	public Result<OrderInfo> miaosha(Model model, MiaoshaUser user,
									 @RequestParam("goodsId")long goodsId) {
		model.addAttribute("user", user);
		if(user == null || user.getId() == null) {
			return Result.error(CodeMsg.SESSION_ERROR);
		}
		// 查询该秒杀商品的库存数量
		GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
		int stock = goods.getStockCount();
		if(stock <= 0) {
			return Result.error(CodeMsg.MIAO_SHA_OVER);
		}
		// 判断是否已经秒杀到了，是否是重复秒杀
		MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
		if(order != null) {
			return Result.error(CodeMsg.REPEAT_MIAOSHA);
		}
		// 当该用户秒杀到该商品时，我们做如下的三步操作：减库存 下订单 写入秒杀订单
		OrderInfo orderInfo = miaoshaService.miaosha(user, goods);
		return Result.success(orderInfo);
	}*/

}



































